// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/dns/host_resolver_proc.h"

#include "build/build_config.h"

#include "base/logging.h"
#include "base/sys_byteorder.h"
#include "net/base/address_list.h"
#include "net/base/net_errors.h"
#include "net/base/sys_addrinfo.h"
#include "net/dns/dns_reloader.h"
#include "net/dns/dns_util.h"

#ifdef TENCENTCHANGES
#include "net/base/address_list.h"
#include "net/tencent/http_dns_resolver.h"
#include "net/tencent/net_logger.h"
#include "net/tencent/udp_dns_resolver.h"
#include <pthread.h>
#endif

#if defined(OS_OPENBSD)
#define AI_ADDRCONFIG 0
#endif

namespace net {

namespace {

    bool IsAllLocalhostOfOneFamily(const struct addrinfo* ai)
    {
        bool saw_v4_localhost = false;
        bool saw_v6_localhost = false;
        for (; ai != NULL; ai = ai->ai_next) {
            switch (ai->ai_family) {
            case AF_INET: {
                const struct sockaddr_in* addr_in = reinterpret_cast<struct sockaddr_in*>(ai->ai_addr);
                if ((base::NetToHost32(addr_in->sin_addr.s_addr) & 0xff000000) == 0x7f000000)
                    saw_v4_localhost = true;
                else
                    return false;
                break;
            }
            case AF_INET6: {
                const struct sockaddr_in6* addr_in6 = reinterpret_cast<struct sockaddr_in6*>(ai->ai_addr);
                if (IN6_IS_ADDR_LOOPBACK(&addr_in6->sin6_addr))
                    saw_v6_localhost = true;
                else
                    return false;
                break;
            }
            default:
                NOTREACHED();
                return false;
            }
        }

        return saw_v4_localhost != saw_v6_localhost;
    }

} // namespace

HostResolverProc* HostResolverProc::default_proc_ = NULL;

HostResolverProc::HostResolverProc(HostResolverProc* previous)
{
    SetPreviousProc(previous);

    // Implicitly fall-back to the global default procedure.
    if (!previous)
        SetPreviousProc(default_proc_);
}

HostResolverProc::~HostResolverProc()
{
}

int HostResolverProc::ResolveUsingPrevious(
    const std::string& host,
    AddressFamily address_family,
    HostResolverFlags host_resolver_flags,
    AddressList* addrlist,
    int* os_error)
{
    if (previous_proc_.get()) {
        return previous_proc_->Resolve(
            host, address_family, host_resolver_flags, addrlist, os_error);
    }

    // Final fallback is the system resolver.
    return SystemHostResolverCall(host, address_family, host_resolver_flags,
        addrlist, os_error);
}

void HostResolverProc::SetPreviousProc(HostResolverProc* proc)
{
    HostResolverProc* current_previous = previous_proc_.get();
    previous_proc_ = NULL;
    // Now that we've guaranteed |this| is the last proc in a chain, we can
    // detect potential cycles using GetLastProc().
    previous_proc_ = (GetLastProc(proc) == this) ? current_previous : proc;
}

void HostResolverProc::SetLastProc(HostResolverProc* proc)
{
    GetLastProc(this)->SetPreviousProc(proc);
}

// static
HostResolverProc* HostResolverProc::GetLastProc(HostResolverProc* proc)
{
    if (proc == NULL)
        return NULL;
    HostResolverProc* last_proc = proc;
    while (last_proc->previous_proc_.get() != NULL)
        last_proc = last_proc->previous_proc_.get();
    return last_proc;
}

// static
HostResolverProc* HostResolverProc::SetDefault(HostResolverProc* proc)
{
    HostResolverProc* old = default_proc_;
    default_proc_ = proc;
    return old;
}

// static
HostResolverProc* HostResolverProc::GetDefault()
{
    return default_proc_;
}

int SystemHostResolverCall(const std::string& host,
    AddressFamily address_family,
    HostResolverFlags host_resolver_flags,
    AddressList* addrlist,
    int* os_error)
{
    // Make sure |host| is properly formed.
    {
        std::string out_ignored;
        if (!DNSDomainFromDot(host, &out_ignored))
            return ERR_NAME_NOT_RESOLVED;
    }

    if (os_error)
        *os_error = 0;

    struct addrinfo* ai = NULL;
    struct addrinfo hints = { 0 };

    switch (address_family) {
    case ADDRESS_FAMILY_IPV4:
        hints.ai_family = AF_INET;
        break;
    case ADDRESS_FAMILY_IPV6:
        hints.ai_family = AF_INET6;
        break;
    case ADDRESS_FAMILY_UNSPECIFIED:
        hints.ai_family = AF_UNSPEC;
        break;
    default:
        NOTREACHED();
        hints.ai_family = AF_UNSPEC;
    }

#if defined(OS_WIN)
    // DO NOT USE AI_ADDRCONFIG ON WINDOWS.
    //
    // The following comment in <winsock2.h> is the best documentation I found
    // on AI_ADDRCONFIG for Windows:
    //   Flags used in "hints" argument to getaddrinfo()
    //       - AI_ADDRCONFIG is supported starting with Vista
    //       - default is AI_ADDRCONFIG ON whether the flag is set or not
    //         because the performance penalty in not having ADDRCONFIG in
    //         the multi-protocol stack environment is severe;
    //         this defaulting may be disabled by specifying the AI_ALL flag,
    //         in that case AI_ADDRCONFIG must be EXPLICITLY specified to
    //         enable ADDRCONFIG behavior
    //
    // Not only is AI_ADDRCONFIG unnecessary, but it can be harmful.  If the
    // computer is not connected to a network, AI_ADDRCONFIG causes getaddrinfo
    // to fail with WSANO_DATA (11004) for "localhost", probably because of the
    // following note on AI_ADDRCONFIG in the MSDN getaddrinfo page:
    //   The IPv4 or IPv6 loopback address is not considered a valid global
    //   address.
    // See http://crbug.com/5234.
    //
    // OpenBSD does not support it, either.
    hints.ai_flags = 0;
#else
    hints.ai_flags = AI_ADDRCONFIG;
#endif

    // On Linux AI_ADDRCONFIG doesn't consider loopback addreses, even if only
    // loopback addresses are configured. So don't use it when there are only
    // loopback addresses.
    if (host_resolver_flags & HOST_RESOLVER_LOOPBACK_ONLY)
        hints.ai_flags &= ~AI_ADDRCONFIG;

    if (host_resolver_flags & HOST_RESOLVER_CANONNAME)
        hints.ai_flags |= AI_CANONNAME;

    // Restrict result set to only this socket type to avoid duplicates.
    hints.ai_socktype = SOCK_STREAM;

#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) && !defined(OS_ANDROID)
    DnsReloaderMaybeReload();
#endif
    int err = getaddrinfo(host.c_str(), NULL, &hints, &ai);
    bool should_retry = false;
    // If the lookup was restricted (either by address family, or address
    // detection), and the results where all localhost of a single family,
    // maybe we should retry.  There were several bugs related to these
    // issues, for example http://crbug.com/42058 and http://crbug.com/49024
    if ((hints.ai_family != AF_UNSPEC || hints.ai_flags & AI_ADDRCONFIG) && err == 0 && IsAllLocalhostOfOneFamily(ai)) {
        if (host_resolver_flags & HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6) {
            hints.ai_family = AF_UNSPEC;
            should_retry = true;
        }
        if (hints.ai_flags & AI_ADDRCONFIG) {
            hints.ai_flags &= ~AI_ADDRCONFIG;
            should_retry = true;
        }
    }
    if (should_retry) {
        if (ai != NULL) {
            freeaddrinfo(ai);
            ai = NULL;
        }
        err = getaddrinfo(host.c_str(), NULL, &hints, &ai);
    }

    if (err) {
#if defined(OS_WIN)
        err = WSAGetLastError();
#endif

        // Return the OS error to the caller.
        if (os_error)
            *os_error = err;

            // If the call to getaddrinfo() failed because of a system error, report
            // it separately from ERR_NAME_NOT_RESOLVED.
#if defined(OS_WIN)
        if (err != WSAHOST_NOT_FOUND && err != WSANO_DATA)
            return ERR_NAME_RESOLUTION_FAILED;
#elif defined(OS_POSIX) && !defined(OS_FREEBSD)
        if (err != EAI_NONAME && err != EAI_NODATA)
            return ERR_NAME_RESOLUTION_FAILED;
#endif

        return ERR_NAME_NOT_RESOLVED;
    }

#if defined(OS_ANDROID)
    // Workaround for Android's getaddrinfo leaving ai==NULL without an error.
    // http://crbug.com/134142
    if (ai == NULL)
        return ERR_NAME_NOT_RESOLVED;
#endif

    *addrlist = AddressList::CreateFromAddrinfo(ai);
    freeaddrinfo(ai);
    return OK;
}

SystemHostResolverProc::SystemHostResolverProc()
    : HostResolverProc(NULL)
{
}

int SystemHostResolverProc::Resolve(const std::string& hostname,
    AddressFamily address_family,
    HostResolverFlags host_resolver_flags,
    AddressList* addr_list,
    int* os_error)
{
#ifdef TENCENTCHANGES
    int dnsType = addr_list->getDnsType();
    NET_LOG("Begin to resolve host <%s> with dnsType <%d>", hostname.c_str(), dnsType);
    int error = -1;
    if (dnsType == DNS_RESOLVE_TYPE_HTTP_DES) {
        std::string results_str;
        error = HttpDnsResolver::getInstance()->parseDNSInfo(hostname.c_str(), address_family, results_str, addr_list, os_error);

        if (error != 0) {
            error = SystemHostResolverCall(hostname,
                address_family,
                host_resolver_flags,
                addr_list,
                os_error);
            addr_list->setDnsType(DNS_RESOLVE_TYPE_LOCAL_DNS);
            addr_list->setDnsServer("localdns");
        } else {
            addr_list->setDnsType(DNS_RESOLVE_TYPE_HTTP_DES);
            addr_list->setDnsServer(HttpDnsResolver::getTencentDnsServer());
        }
    } else if (dnsType == DNS_RESOLVE_TYPE_UDP) {
        error = UdpDnsResolver::getInstance()->parseDNSInfo(hostname.c_str(), address_family, host_resolver_flags, addr_list, os_error);

        if (error != 0) {
            error = SystemHostResolverCall(hostname,
                address_family,
                host_resolver_flags,
                addr_list,
                os_error);
            addr_list->setDnsType(DNS_RESOLVE_TYPE_LOCAL_DNS);
            addr_list->setDnsServer("localdns");
        } else {
            addr_list->setDnsType(DNS_RESOLVE_TYPE_UDP);
            addr_list->setDnsServer(UdpDnsResolver::getTencentDnsServer());
        }
    } else {
        error = SystemHostResolverCall(hostname,
            address_family,
            host_resolver_flags,
            addr_list,
            os_error);
        addr_list->setDnsType(DNS_RESOLVE_TYPE_LOCAL_DNS);
        addr_list->setDnsServer("localdns");
    }

    return error;
#else
    return SystemHostResolverCall(hostname,
        address_family,
        host_resolver_flags,
        addr_list,
        os_error);
#endif
}

SystemHostResolverProc::~SystemHostResolverProc() { }

} // namespace net
